package com.king.hpeproject.filter;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.king.hpeproject.constant.BaseResponse;
import com.king.hpeproject.constant.RequestContext;
import com.king.hpeproject.constant.RequestWrapper;
import com.king.hpeproject.entity.perm.AccountDo;
import com.king.hpeproject.entity.perm.OrgWithAccountDo;
import com.king.hpeproject.entity.perm.ProjectDo;
import com.king.hpeproject.entity.perm.ProjectWithAccountDo;
import com.king.hpeproject.mapper.AccountMapper;
import com.king.hpeproject.mapper.OrgWithAccountMapper;
import com.king.hpeproject.mapper.ProjectMapper;
import com.king.hpeproject.mapper.ProjectWithAccountMapper;
import com.king.hpeproject.service.JwtService;
import com.king.hpeproject.service.perm.OrganizationService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * HpeProtectionFilter 过滤器：横向越权防护
 *
 * @author zepeng_guo
 * @since 2025/8/12
 */
@Slf4j
@WebFilter(urlPatterns = "/system/*")
@Order(10)
public class HpeProtectionFilter extends OncePerRequestFilter {
    private static final PathMatcher pathMatcher = new AntPathMatcher();

    /**
     * -1查询当前用户下所有有权限的资源
     */
    private static final Long ID_WITH_FIND_ALL_AUTHORIZED_RESOURCES = -1L;

    /**
     * 忽略接口
     */
    private static final List<String> EXCLUDE_URLS = Arrays.asList(
            "/system/org/*/project/*/login/account/v2",
            "/system/org/*/project/*/login/account",
            "/system/org/*/project/*/logout",
            "/system/org/*/project/*/getVerify",
            "/system/org/*/project/*/send-login-email");

    /**
     * 路径 orgId
     */
    private static final String PATH_ORG_ID = "orgId";

    /**
     * 路径 项目id
     */
    private static final String PATH_PROJECT_ID = "projectId";

    /**
     * 前端标准url规则
     */
    private static final String SYSTEM_URL_STANDARD = "/system/org/{" + PATH_ORG_ID + "}/project/{" + PATH_PROJECT_ID + "}/**";

    @Resource
    private JwtService jwtService;

    @Resource
    private OrgWithAccountMapper orgWithAccountMapper;

    @Resource
    private AccountMapper accountMapper;

    @Resource
    private ProjectWithAccountMapper projectWithAccountMapper;

    @Resource
    private ProjectMapper projectMapper;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private OrganizationService organizationService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        try {
            // 排除url
            if (isExcluded(request.getRequestURI())) {
                chain.doFilter(request, response);
                return;
            }

            // 上下文
            RequestContext context = RequestContext.getCurrentContext();

            // 检查url规则，并提取出组织id和项目id
            Map<String, String> orgIdAndProjectIdMap = extractProjectMap(request.getRequestURI());
            Long orgId = Long.valueOf(orgIdAndProjectIdMap.get(PATH_ORG_ID));
            Long projectId = Long.valueOf(orgIdAndProjectIdMap.get(PATH_PROJECT_ID));
            log.info("HpeProtectionFilter orgId={}, projectId={}", orgId, projectId);

            // 解析userId
            Long userId = jwtService.getUserIdFromToken(request);
            if (userId == null) {
                throw new UnauthorizedException("无效 Token");
            }

            // 检查项目越权
            checkProjectPE(orgId, projectId, userId);

            fillToContext(context, userId, orgId, projectId);

            // 使用可重复读取的 Request
            RequestWrapper requestWrapper = new RequestWrapper(request);

            // 校验通过，放行请求
            chain.doFilter(requestWrapper, response);
        } catch (UnauthorizedException unauthorizedException) {
            log.error("HpeProtectionFilter UnauthorizedException: ", unauthorizedException);
            response.setStatus(HttpStatus.FORBIDDEN.value());
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);

            BaseResponse<?> errorResponse = BaseResponse.error(
                    HttpStatus.FORBIDDEN.value(),
                    unauthorizedException.getMessage()
            );

            response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
        } catch (RuntimeException runtimeException) {
            log.error("HpeProtectionFilter RuntimeException: ", runtimeException);
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);

            BaseResponse<?> errorResponse = BaseResponse.error(
                    HttpStatus.INTERNAL_SERVER_ERROR.value(),
                    runtimeException.getMessage()
            );

            response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
        } finally {     // 清理 ThreadLocal，防止内存泄漏！
            RequestContext.clear();
        }
    }

    /**
     * 填充信息到上下文
     *
     * @param context 上下文
     * @param userId 用户id
     * @param orgId 组织id
     * @param projectId 项目id
     */
    private void fillToContext(RequestContext context, Long userId, Long orgId, Long projectId) {
        context.setUserId(userId);
        AccountDo account = accountMapper.selectById(userId);
        Optional.ofNullable(account).map(AccountDo::getAccountName).ifPresent(context::setUserName);

        context.setOrgId(orgId);
        context.setIsHead(organizationService.isHead(orgId));

        // 填充项目id到上下文
        if (!ID_WITH_FIND_ALL_AUTHORIZED_RESOURCES.equals(projectId)) {
            context.setProjectIds(Collections.singletonList(projectId));
        } else if(context.getIsHead()) {    // 总公司可以操作任何资源
            context.setProjectIds(Collections.emptyList());
        } else {
            LambdaQueryWrapper<ProjectWithAccountDo> projectWithAccountWrapper = Wrappers.lambdaQuery();
            projectWithAccountWrapper.eq(ProjectWithAccountDo::getAccountId, userId);
            projectWithAccountWrapper.select(ProjectWithAccountDo::getProjectId);
            List<Long> projectIds = projectWithAccountMapper.selectList(projectWithAccountWrapper)
                    .stream().map(ProjectWithAccountDo::getProjectId).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(projectIds)) {
                throw new UnauthorizedException("当前用户不在任何项目下");
            }
            context.setProjectIds(projectIds);
        }
    }

    /**
     * 检查项目越权
     *
     * @param orgId 组织id
     * @param projectId 项目id
     * @param userId 用户id
     */
    private void checkProjectPE(Long orgId, Long projectId, Long userId) {
        // 检查组织越权
        LambdaQueryWrapper<OrgWithAccountDo> lambdaQueryWrapper = Wrappers.lambdaQuery();
        lambdaQueryWrapper.eq(OrgWithAccountDo::getOrgId, orgId);
        lambdaQueryWrapper.eq(OrgWithAccountDo::getAccountId, userId);
        Integer orgWithAccountCount = orgWithAccountMapper.selectCount(lambdaQueryWrapper);
        if (orgWithAccountCount <= 0) {
            throw new UnauthorizedException("当前组织没有操作权限");
        }

        // 检查项目越权
        if (!ID_WITH_FIND_ALL_AUTHORIZED_RESOURCES.equals(projectId)) {
            // 检查组织项目关系
            LambdaQueryWrapper<ProjectDo> projectWrapper = Wrappers.lambdaQuery();
            projectWrapper.eq(ProjectDo::getOrganizationId, orgId);
            projectWrapper.eq(ProjectDo::getProjectId, projectId);
            Integer projectCount = projectMapper.selectCount(projectWrapper);
            if (projectCount <= 0) {
                throw new UnauthorizedException("当前组织下不存在该项目");
            }

            LambdaQueryWrapper<ProjectWithAccountDo> projectWithAccountWrapper = Wrappers.lambdaQuery();
            projectWithAccountWrapper.eq(ProjectWithAccountDo::getProjectId, projectId);
            projectWithAccountWrapper.eq(ProjectWithAccountDo::getAccountId, userId);
            Integer projectWithAccountCount = projectWithAccountMapper.selectCount(projectWithAccountWrapper);
            if (projectWithAccountCount <= 0) {
                throw new UnauthorizedException("当前项目没有操作权限");
            }
        }
    }

    /**
     * 提取项目信息
     *
     * @param requestUri uri
     * @return 项目信息
     */
    public static Map<String, String> extractProjectMap(String requestUri) {
        if (pathMatcher.match(SYSTEM_URL_STANDARD, requestUri)) {
            return pathMatcher.extractUriTemplateVariables(SYSTEM_URL_STANDARD, requestUri);
        }
        throw new RuntimeException("URL不符合标准格式");
    }

    /**
     * 是否排除当前uri
     *
     * @param requestURI uri
     * @return 是否排除
     */
    private boolean isExcluded(String requestURI) {
        return EXCLUDE_URLS.stream().anyMatch(exclude -> {
            if (exclude.contains("/*/")) {
                // 将模式转换为正则表达式，将/*/替换为/[^/]+/
                String regex = exclude.replaceAll("/\\*/", "/[^/]+/").replaceAll("/\\*$", "/[^/]+");
                return requestURI.matches(regex);
            }
            return requestURI.equals(exclude);
        });
    }
}